package com.appology.jokes;

import java.util.Random;
import java.util.List;
import java.util.ArrayList;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Query;

public class Joke {

	public static final String ENTITY_TYPE = "Jokes";
	public static final String COLUMN_CONTENT = "content";
	public static final String COLUMN_AID = "aId";

	private static final String REPLACEMENT_NAME = "TKname";
	private static final String DEFAULT_NAME = "Chuck Norris";
	private static final String SPAN_OPENING = "<span class=\"name\">";
	private static final String SPAN_CLOSING = "</span>";

	private static Joke instance = null;
	private static DatastoreService datastore;
	private static Random generator;
	private static Entity joke;

	/**
	 * This constructor is only meant to be called by getInstance()
	 */
	protected Joke() {
		// Get our random number generator, datastore and query on the "Jokes"
		// entity type
		generator = new Random();
		datastore = DatastoreServiceFactory.getDatastoreService();

	}

	/**
	 * Use Joke.getInstance() to get a singleton Joke
	 * 
	 * @return An instance of Joke
	 */
	public static Joke getInstance() {
		if (instance == null) {
			instance = new Joke();
		}
		return instance;
	}

	/**
	 * Takes a name given to it, and gets a joke from the database, replacing
	 * instances of "TKname" in the joke with
	 * 
	 * @param name
	 * @return
	 */
	public String getRandom(String name) {

		// If a null name was passed in, use the default name instead, which is
		// Chuck Norris
		if (name == null) {
			name = DEFAULT_NAME;
		}

		// Start with a maximum of one because generator.nextInt() throws an
		// exception if it is called with a non-positive number.
		int max = 1;

		// Get the number of jokes in the datastore. If it is greater than zero,
		// set max to that number, otherwise, leave max at 1
		int count = getCount();
		if (count >= 0) {
			max = count;
		}

		// Get a random integer between 0 and the number of jokes in the
		// datastore
		// Then add one because nextInt is inclusive of 0 and exclusive of max.
		// Our datastore is 1 indexed, so this ensures that we won't
		// accidentally try to get the joke at index 0, and that the last joke
		// in the datastore will get called sometimes
		int randomIndex = generator.nextInt(max) + 1;

		// Format the joke and return it
		return formatJoke(getJokeFromDB(randomIndex), name);
	}

	/**
	 * Formats a joke given the raw joke and a name to be inserted.
	 * 
	 * @param unformattedJoke
	 *            A joke with "TKname" still in the text
	 * @param name
	 *            A name to be inerted into the joke
	 * @return A joke that is formatted wth span tags around the name
	 */
	private String formatJoke(String unformattedJoke, String name) {

		// Build a string that has the name between two span tags with the
		// "name" class so we can do some styling with it later
		StringBuilder builder = new StringBuilder();
		builder.append(SPAN_OPENING);
		builder.append(name);
		builder.append(SPAN_CLOSING);

		// Replace all occurences of "TKname" with the string we constructed and
		// return it
		return unformattedJoke.replaceAll(REPLACEMENT_NAME, builder.toString());
	}

	/**
	 * Counts the number of items in the DB
	 * 
	 * @return The integer number of jokes in the datastore
	 */
	public int getCount() {
		Query query = new Query(ENTITY_TYPE);
		return datastore.prepare(query).countEntities(
				FetchOptions.Builder.withDefaults());
	}

	/**
	 * Gets the Joke from the datastore by it's aId
	 * 
	 * @param ranId
	 *            This integer should be greater than 0 and less than or equal
	 *            to the number of jokes in the database
	 * @return A String value containing an unmodified joke
	 */
	public String getJokeFromDB(int ranId) {
		String answer;

		Query query = new Query(ENTITY_TYPE);

		// Query the datastore for a joke where the aId matches the ranId value
		// passed in
		query.addFilter(COLUMN_AID, Query.FilterOperator.EQUAL, ranId);

		// execute the query and put the result into an entity
		joke = datastore.prepare(query).asSingleEntity();

		// If the returned entity is null, then send back a funny error message
		if (joke == null) {
			answer = "Chuck Norris roundhouse kicked the joke out of the database.  Try again if you dare.";
		} else {
			// Otherwise, send back the joke cast to a String
			answer = (String) joke.getProperty(COLUMN_CONTENT);
		}

		return answer;
	}

	/**
	 * Adds the Chuck Norris Jokes to the DB
	 * 
	 * @return No return value
	 */

	public void AddItemsToDB() {

		/**
		 * Load all the jokes into the Google datastore
		 */
		for (int i = 0; i < JokeHelper.alist.size(); i++) {

			// Instantiate a new joke with a key equal to its index in the jokes
			// list
			joke = new Entity(ENTITY_TYPE, i + 1);

			// Set the content of the joke to the joke at the current index
			joke.setProperty(COLUMN_CONTENT, JokeHelper.alist.get(i));

			// Use an aId so that we can guarantee that the id will fall into
			// our random number generator
			// ID - is auto-incremented therefore can be problematic
			joke.setProperty(COLUMN_AID, i + 1);

			// Add the joke to the datastore
			datastore.put(joke);
		}
	}
}
